package modularity.passiveDrift;

import java.util.ArrayList;
import java.util.HashMap;

import ec.util.MersenneTwisterFast;

public class ModuleMutator implements Mutator {

	protected BasicMutator basicMutator;
	private double mergeModuleProb;
	private double splitModuleProb;
	private double duplicateModuleProb;
	private double removeModuleProb;
	
	public ModuleMutator(BasicMutator basicMutator, double mergeModuleProb, double splitModuleProb, double duplicateModuleProb, double removeModuleProb){
		this.basicMutator = basicMutator;
		this.mergeModuleProb = mergeModuleProb;
		this.splitModuleProb = splitModuleProb;
		this.duplicateModuleProb = duplicateModuleProb;
		this.removeModuleProb = removeModuleProb;
	}
	
	public void mutate(Genome<ConnectionGene> gen, MersenneTwisterFast random){
		ModularGenome genome = (ModularGenome) gen;

		for(int i = 0; i < genome.getNumberOfModules(); i++){
			//mutate each module
			basicMutator.mutate(genome.get(i), random);
		}

		duplicateModule(genome, random);
		removeModule(genome, random);
		mergeModule(genome, random);
		splitModule(genome, random);
	}

	private void splitModule(ModularGenome genome, MersenneTwisterFast random) {
		// TODO Auto-generated method stub
	}

	private void mergeModule(ModularGenome genome, MersenneTwisterFast random) {
		double prob = random.nextDouble();
		if(prob < mergeModuleProb){
			int index1 = random.nextInt(genome.getNumberOfModules());
			int index2 = random.nextInt(genome.getNumberOfModules());

			while(index2 == index1){
				index2 = random.nextInt(genome.getNumberOfModules());
			}
			//to merge two modules, we remove one from the genome and add all its structure to the other.
			BasicGenome g1 = genome.remove(index1);
			BasicGenome g2 = genome.get(index2);
			for(int i = 0; i < g1.getNumberOfConnections(); i++){
				g2.addConnection(g1.getConnectionAtIndex(i));
			}
		}
	}

	public void removeModule(ModularGenome genome, MersenneTwisterFast random){
		double prob = random.nextDouble();
		if(prob < removeModuleProb){
			//remove a randomly chosen module.
			int index = random.nextInt(genome.getNumberOfModules());
			genome.remove(index);
		}
	}

	public void duplicateModule(ModularGenome genome, MersenneTwisterFast random){
		double prob = random.nextDouble();
		if(prob < duplicateModuleProb){
			//duplicate a randomly chosen module.
			int index = random.nextInt(genome.getNumberOfModules());
			BasicGenome toDuplicate = genome.get(index);

			BasicGenome duplicated = duplicate(toDuplicate, random);
			genome.add(duplicated);
		}
	}

	/**
	 * IMPORTANT DETAIL: in the duplication process, we reassign a new id to each node gene in each connection being duplicated,
	 * thus ensuring that there are no conflicts and no ARTIFACTS caused by duplication of structure.
	 */
	private BasicGenome duplicate(BasicGenome toDuplicate, MersenneTwisterFast random) {
		BasicGenome duplicate = new BasicGenome();

		//create the new ids of nodes
		HashMap<Integer, Integer> idConversion = new HashMap<Integer, Integer>();
		ArrayList<Integer> nodes = toDuplicate.getListOfNodes();
		for(Integer node : nodes){
			if(!idConversion.containsKey(node)){
				idConversion.put(node, basicMutator.generateNextNodeId());
			}
		}

		for(int i = 0; i < toDuplicate.getNumberOfConnections(); i++){
			ConnectionGene current = toDuplicate.getConnectionAtIndex(i);
			int fromGene = current.getFromGene();
			int toGene = current.getToGene();

			ConnectionGene duplicatedConnection = new ConnectionGene(idConversion.get(fromGene), idConversion.get(toGene)); 
			duplicate.addConnection(duplicatedConnection);
		}
		return duplicate;
	}
}
